package com.bubblegumproject.ogo.swing;

import com.bubblegumproject.ogo.action.Action;
import com.bubblegumproject.ogo.action.*;
import com.bubblegumproject.ogo.component.Component;
import com.bubblegumproject.ogo.component.Container;
import com.bubblegumproject.ogo.component.injection.Inject;
import com.bubblegumproject.ogo.swing.events.ConditionActionMap;
import org.apache.log4j.Logger;

import javax.swing.*;
import javax.swing.event.EventListenerList;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Executor;

/**
 * A UIActivity is a significant duration of the user interface. It's an object with an extended lifecycle that, over
 * time, must respond to many different user interface interactions from the user and/or actions generated from other
 * aspects of the application.
 *
 * TODO: Let's get rid of UIProcess. This class will implement Flow and all action handlers will be defined in the
 * UIActivity itself. There's just no benefit to having a separate UIProcess class.
 *
 * @author Azubuko Obele (buko.obele@gmail.com)
 */
@Component
public abstract class UIActivity implements Reactor, Flow {

    protected Context context;

    protected Container container;

    protected ActionHandlerInvoker actionHandlerInvoker;

    protected ConditionActionMap conditions = new ConditionActionMap(this);

    protected transient EventListenerList listeners = new EventListenerList();

    protected boolean finished = false;

    protected UIActivity parent;

    /**
     * Executor that may be used to perform background work in the Activity. All work will occur off the EDT.
     */
    protected Executor backgroundExecutor;

    private static final Logger log = Logger.getLogger(UIActivity.class);

    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public Container getContainer() {
        return container;
    }

    @Inject
    public void setContainer(Container container) {
        this.container = container;
    }

    public Executor getBackgroundExecutor() {
        return backgroundExecutor;
    }

    public UIActivity getParent() {
        return parent;
    }

    @Inject(optional = true)
    public void setParent(UIActivity parent) {
        this.parent = parent;
    }

    @Inject(optional = true)
    public void setBackgroundExecutor(Executor backgroundExecutor) {
        this.backgroundExecutor = backgroundExecutor;
    }


    public boolean isFinished() {
        return finished;
    }
    
    // @PostConstruct -- lifecycle methods called by activity's parent, NOT THE CONTAINER!!!!
    public void start() {
        // if the context hasn't been explicitly set inherit it from the parent and initialize it
        initializeContext();

        actionHandlerInvoker = new SimpleActionHandlerInvoker(container);
        fireStarted();
    }

    protected void initializeContext() {
        // if the user manually assigns a Context to the component we must inherit from it
        if (context != null) {
            context = new SimpleContext(context);
        } else if (parent != null) {
            // otherwise inherit the context from the parent
            context = new SimpleContext(parent.getContext());
        } else {
            context = new SimpleContext(null);
        }
        context.set(container.getClass().getName(), container);
        context.set(Executor.class.getName(), backgroundExecutor);
        // shold inject the parent-- might want to name this dependency to be sure
        context.set(UIActivity.class.getName(), this);
    }

    /**
     * Invoked to stop the UIActivity. A UIActivity is stopped when no further interaction is required and no further
     * Action objects will be addressed to it. Note that generally, because UIActivity objects should be as
     * self-contained as possible, it's up to subclasses themselves to invoke this method when they are finished.
     * Clients of the UIActivity will usually do nothing but create it, start it, and then listen for its completion.
     *
     * XXX: This is just wrong. UIActivities certainly should not have a reference back to their parents. Instead when
     * a UIActivity spawns a child it should add a listener that can listen for the child's stopping. No more references
     * to parents anywhere!
     */
    // @PreDestroy
    public void stop() {
        if (parent != null) parent.react(new UIActivityStoppedAction(this));
        finished = true;
        fireStopped();
    }

    public <T extends UIActivity> T spawn(Class<T> activityClass) {
        assert (activityClass != null);
        if (!container.contains(activityClass)) container.register(activityClass);
        return container.create(activityClass, context);
    }

    public void destroy(UIActivity activity) {
        container.destroy(activity);
    }

    public Action react(final Action action) {
        assert (action != null);
        if (isFinished()) {
            log.warn(this + " ignoring action " + action + " since activity is finished");
            return null;
        }

        // We make the guarantee that all action handlers are invoked on the Swing EDT. This makes it
        // much easier to the developer who will be continually updating the gui in his application logic
        final boolean[] handled = new boolean[1];
        if (!SwingUtilities.isEventDispatchThread()) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    public void run() {
                        handled[0] = actionHandlerInvoker.invoke(action, UIActivity.this);
                    }
                });
            } catch (InvocationTargetException ite) {
                log.warn("ActionHandler raised exception not on EDT thread", ite.getCause());
            } catch (InterruptedException ie) {
                log.warn(this + " got unexpected exception", ie);
            }
        } else {
            handled[0] = actionHandlerInvoker.invoke(action, this);
        }
        
        if (!handled[0]) log.info(this + " ignored action " + action + " because no handler could be found");
        return action;
    }
    
    public void addUIActivityListener(UIActivityListener l) {
        listeners.add(UIActivityListener.class, l);
    }

    public void removeUIActivityListener(UIActivityListener l) {
        listeners.remove(UIActivityListener.class, l);
    }

    protected void fireStarted() {
        UIActivityEvent e = new UIActivityEvent(this);
        for (UIActivityListener l : listeners.getListeners(UIActivityListener.class)) {
            l.activityStarted(e);
        }
    }

    protected void fireStopped() {
        UIActivityEvent e = new UIActivityEvent(this);
        for (UIActivityListener l : listeners.getListeners(UIActivityListener.class)) {
            l.activityStopped(e);
        }
    }
}
